/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: cbus_transportc
 *
 * Purpose: transport manager or helper implementation for cbus.
 *
 * Developer:
 *   wen.gu , 2022-01-18
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "cbus/bus/cbus_transport.h"

#include <string.h>
#include "cbus/core/cbus_mutex.h"

#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
typedef struct _cbus_transport_node
{
    struct _cbus_transport_node* next;
    const cbus_transport_ops* trans_ops;
}cbus_transport_node_t;


typedef GErrc (*cbus_transport_ctor)(void);

static cbus_mutex_t g_trans_lock = NULL;
static GU32 g_is_trans_inited = 0;
static cbus_transport_node_t g_trans_list_head = {0};

#ifdef CBUS_TRANSPORT_IPC
extern GErrc cbus_uds_transport_default_ctor(void);
#endif


static const cbus_transport_ctor g_trans_default_ctors[] = 
{
#ifdef CBUS_TRANSPORT_IPC
    cbus_uds_transport_default_ctor,
#endif
    NULL,
};
/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/
static GErrc cbus_transport_default_ctor(void)
{
    for (GU32 i = 0; g_trans_default_ctors[i] != NULL; i++)
    {
        GErrc ret = g_trans_default_ctors[i]();

        if (G_OK != ret)
        {
            LOGE("do default ctor failed(idx: %d, ret: %d)\n", i, ret);
            return ret;
        }
    }
    
    return G_OK;
}

static void cbus_transport_list_free()
{
    cbus_mutex_lock(g_trans_lock);
    cbus_transport_node_t* item = g_trans_list_head.next;
    while (item != NULL)
    {
        if (item->trans_ops && item->trans_ops->finish)
        {/** todo, is need make sure all listener and dialer are finished */
            item->trans_ops->finish();
        }
        cbus_transport_node_t* next = item->next;
        free(item);
        item = next;
    }

    if (g_trans_list_head.trans_ops && g_trans_list_head.trans_ops->finish)
    {
        g_trans_list_head.trans_ops->finish();
    }

    g_trans_list_head.next = NULL;
    g_trans_list_head.trans_ops = NULL;
    cbus_mutex_unlock(g_trans_lock);
}
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

/**
 * @brief initislize the default transports
 * 
 * @return success: G_OK, else: error code @see GErrc
 */
GErrc cbus_transport_initialize(void)
{
    if ((g_is_trans_inited == 0) && (g_trans_lock == NULL))
    {
        g_trans_lock = cbus_mutex_create();
        cbus_mutex_lock(g_trans_lock);
        g_trans_list_head.next = NULL;
        g_trans_list_head.trans_ops = NULL;
        g_is_trans_inited = 1;
        cbus_mutex_unlock(g_trans_lock);
        GErrc ret = cbus_transport_default_ctor();
        if (ret != G_OK)
        {
            cbus_transport_finish();
            return ret; 
        }
        return G_OK;
    }

    return G_ErrInvalidStatus;
}

/**
 * @brief uninitialize tansport
 * @param void none
 * @return void
 */
void cbus_transport_finish(void)
{
    if ((g_is_trans_inited) && (g_trans_lock))
    {
        cbus_transport_list_free();
        g_is_trans_inited = 0;     
        cbus_mutex_destroy(g_trans_lock); 
        g_trans_lock = NULL;       
    }
}

/**
 * @brief find a valid transport class with name
 * 
 * @param trans_name [in] the name of transport
 * @return cbus_transport_ops* the reference of a transport class
 */
const cbus_transport_ops* cbus_transport_find(const GSTR trans_name)
{
    if (!trans_name)
    {
        return NULL;
    }

    if ((g_is_trans_inited) && (g_trans_lock))
    {
        cbus_mutex_lock(g_trans_lock);
        cbus_transport_node_t* item = &g_trans_list_head;
        while (item != NULL)
        {
            if (item->trans_ops && item->trans_ops->scheme_name)
            {
                if (strcmp(trans_name, item->trans_ops->scheme_name) == 0)
                {
                    cbus_mutex_unlock(g_trans_lock);
                    return item->trans_ops;
                }
            }

            item = item->next;
        }
        cbus_mutex_unlock(g_trans_lock);
    }

    return NULL;
}

/**
 * @brief register a transport class dynamicly
 * 
 * @param cbus_trans [in] a reference of a transport class
 * @return success: G_OK, else error coce @see GErrc
 */
GErrc cbus_transport_register(const cbus_transport_ops* cbus_trans)
{    
    if (!cbus_trans || !cbus_trans->scheme_name)
    {
        return G_ErrBadParameter;
    }

    if (!g_is_trans_inited)
    {
        GErrc ret = cbus_transport_initialize();

        if (G_OK != ret)
        {
            return ret;
        }
    }

    if (cbus_trans->version != CBUS_TRANSPORT_VERSION)
    {
        return G_ErrVersionMismatch;
    }

    cbus_mutex_lock(g_trans_lock);
    /** check exist or goto the end of list */
    cbus_transport_node_t* item = & g_trans_list_head;
    cbus_transport_node_t* tail = NULL;

    while(item != NULL)
    {
        if (item->trans_ops && item->trans_ops->scheme_name)
        {
            if (strcmp(cbus_trans->scheme_name, item->trans_ops->scheme_name) == 0)
            {
                if (cbus_trans->initialize == item->trans_ops->initialize)
                {
                    cbus_mutex_unlock(g_trans_lock);
                    return G_OK; /** if exist, then just return OK, todo refine me?? */
                }
                cbus_mutex_unlock(g_trans_lock);
                return G_ErrInvalidStatus;
            }
        }
        tail = item;
        item = item->next;
    }

    if (tail)
    {
        cbus_transport_node_t* temp = (cbus_transport_node_t*)malloc(sizeof(cbus_transport_node_t));

        if (!temp)
        {
            LOGE("alloc transport node failed\n");
            cbus_mutex_unlock(g_trans_lock);
            return G_ErrInsufficientResources;
        }

        temp->next = NULL;
        temp->trans_ops = cbus_trans;
        tail->next = temp;
        if (cbus_trans->initialize)
        {
            GErrc ret = cbus_trans->initialize();
            if (G_OK != ret)
            {
                LOGE("initialize for transport(%s, ver: 0x%08x) failed\n", cbus_trans->scheme_name, cbus_trans->version);
            }
        }
        cbus_mutex_unlock(g_trans_lock);
        return G_OK;
    }

    cbus_mutex_unlock(g_trans_lock);
    return G_ErrUndefined;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
GErrc cbus_transport_get_scheme_name_by_url(const GSTR url, cbus_transport_scheme_t scheme)
{
    if (!url)
    {
        return G_ErrBadParameter;
    }

	// Grab the scheme.
	const GSTR s = url;
    GS32 url_len = strlen(url);

    GU32 len = 0;
    GCHR c;
	for (len = 0; (c = s[len]) != ':'; len++) 
    {
		if (c == 0) 
        {
			break;
		}
	}

    if (len >= url_len)
    {
        LOGE("invalid url for transport, the url syntax must be: scheme:[//[userinfo@]host][/]path[?query][#fragment]\n");
        return G_ErrInvalidName;
    }

    if (len >= sizeof (cbus_transport_scheme_t))
    {
        return G_ErrOutOfRange;
    }

	if (((len + 3) > url_len) || (strncmp(s + len, "://", 3) != 0) )
    {
        LOGE("invalid url for transport, the url syntax must be: scheme:[//[userinfo@]host][/]path[?query][#fragment]\n");
        return G_ErrInvalidName;
	}

    memcpy(scheme, url, len);
    scheme[len] = '\0';

    return G_OK;
}

